using System;
using System.Data;
using System.Configuration;
using CodeWork.Library.Data;
using System.Collections.Generic;
using System.Transactions;
using CodeWork.Library;
using System.Data.SqlTypes;
using log4net;
using CodeWork.Library.Web;
using System.Text;
using EasyCommerce.DAL;
using System.Web;

namespace EasyCommerce.BLL
{
    /// <summary>
    /// This class contains the Business logic for Site user handling
    /// </summary>
    public class UserManager
    {
        public UserManager() { }

        /// <summary>
        /// Checks whether a username is free for registration or not
        /// </summary>
        /// <param name="userName">Username to check for availability</param>
        /// <returns>True if the username is free, false otherwise</returns>
        public static bool CheckUserNameAvailability(string userName)
        {
            try
            {
                // Checking for valid format of the user name
                if (string.IsNullOrEmpty(userName) || !RegExUtil.IsCustomMatch(userName, RegExUtil.UserNameFormat))
                    return false;

                // Counting the occurence of the username
                SiteUser SiteUser = new SiteUser();
                List<Condition> conditions = new List<Condition>();
                userName = Security.BindStringInput(userName, SecurityLevel.Two);
                conditions.Add(new Condition("user_name", ConditionOperator.Equal, userName, JoinOperator.FirstValue));
                int count = SiteUser.LoadCount(conditions);
                
                // If count is positive, the username is already taken, otherwise free to register
                if (count > 0)
                    return false;
                else
                    return true;
            }
            catch (Exception ex)
            {
                // Setting log information about the exception
                StringBuilder logText = new StringBuilder("userName check caused server error: Parameters( ");
                logText = logText.Append("userName -> ").Append(userName);
                logText = logText.Append(" )");

                // Write the log into handled exception list
                ILog log = LogHelper.GetHandledFileLogger("UserManager");
                if (log.IsErrorEnabled)
                    log.Error(logText.ToString(), ex);

                // return failure
                return false;
            }
        }

        /// <summary>
        /// This method check the userName and confirmation code combination
        /// and returns the first user that found with the match.
        /// Useful for verification of confirmation mails. This method is
        /// for internal use only. Used by other complicated verification
        /// methods.
        /// </summary>
        /// <param name="userName">The account username</param>
        /// <param name="confirmationCode">The confirmation that belongs to that username</param>
        /// <returns>The found user object, otherwise null</returns>
        private static SiteUser VerifyConfirmationCode(string userName, string confirmationCode)
        {
            const int DEFAULT_USER = 0;     // Using the first user, if multiple found
            try
            {
                // Both username and confirmation code must be non empty
                if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(confirmationCode))
                    return null;

                // Load the users that match the conditions ( userName and confirmation code)
                SiteUser SiteUser = new SiteUser();
                List<Condition> conditions = new List<Condition>();

                userName = Security.BindStringInput(userName, SecurityLevel.Two);
                confirmationCode = Security.BindStringInput(confirmationCode, SecurityLevel.Two);

                conditions.Add(new Condition("user_name", ConditionOperator.Equal, userName, JoinOperator.FirstValue));
                conditions.Add(new Condition("confirmation_code", ConditionOperator.Equal, confirmationCode, JoinOperator.And));

                List<SiteUser> SiteUsers = SiteUser.Load(conditions);

                // If atleast one user is found that match the criteria,
                // return the user or return null
                if (SiteUsers.Count > DEFAULT_USER)
                {
                    return SiteUsers[DEFAULT_USER];
                }
                return null;
            }
            catch (Exception ex)
            {
                // Setting log information about the exception
                StringBuilder logText = new StringBuilder("Confirmation code verification caused server error: Parameters( ");
                logText = logText.Append("userName -> ").Append(userName);
                logText = logText.Append(",confirmationCode -> ").Append(confirmationCode);
                logText = logText.Append(" )");

                // Write the log into handled exception list
                ILog log = LogHelper.GetHandledFileLogger("UserManager");
                if (log.IsErrorEnabled)
                    log.Error(logText.ToString(), ex);

                // return failure
                return null;
            }
        }

        /// <summary>
        /// Verifies that registration confirmation mail and activates the 
        /// user account if the verification is successful
        /// </summary>
        /// <param name="userName">username for whom the account is being verified</param>
        /// <param name="confirmationCode">Confirmation code for the verification</param>
        /// <returns>True if the account is successfuly verified and activated</returns>
        public static bool VerifyRegistrationConfirmation(string userName, string confirmationCode)
        {
            try
            {
                // Check the confirmation code belongs to that user or not
                SiteUser SiteUser = VerifyConfirmationCode(userName, confirmationCode);

                // If the user that match the confirmation code is found
                if (SiteUser != null)
                {
                    // Confirm his registration and activate the account
                    SiteUser.IsConfirmed = true;
                    SiteUser.IsActive = true;
                    return SiteUser.Save();
                }
                // send failure if no user is found
                return false;
            }
            catch (Exception ex)
            {
                // Setting log information about the exception
                StringBuilder logText = new StringBuilder("Registration verification caused server error: Parameters( ");
                logText = logText.Append("userName -> ").Append(userName);
                logText = logText.Append(",confirmationCode -> ").Append(confirmationCode);
                logText = logText.Append(" )");

                // Write the log into handled exception list
                ILog log = LogHelper.GetHandledFileLogger("UserManager");
                if (log.IsErrorEnabled)
                    log.Error(logText.ToString(), ex);

                // return failure
                return false;
            }
        }

        /// <summary>
        /// Creates a new account for a new user. The account is not activated
        /// instantly rather a confirmation mail has been sent and after that
        /// mail is verified with other helper method in this class the user
        /// account is activated.
        /// </summary>
        /// <param name="userName">The username choosen for the account</param>
        /// <param name="userPassword">Password for the account</param>
        /// <param name="userEmail">Email address for the account</param>
        /// <param name="firstName">First name of the user</param>
        /// <param name="lastName">Last name of the user</param>
        /// <param name="homeAddress">Home address of the user</param>
        /// <param name="homeCity">Home city of the user</param>
        /// <param name="homeState">Home state of the user</param>
        /// <param name="dateOfBirth">Date of birth of the user</param>
        /// <param name="country">Country code of the user</param>
        /// <param name="postalCode">postal code of the user</param>
        /// <param name="phoneNumber">Phone number of the user</param>
        /// <param name="securityQuestion">Security question that is used in case of verification</param>
        /// <param name="securityAnswer">Answer for the security question</param>
        /// <returns></returns>
        public static bool RegisterSiteUser(string userName, string userPassword, string userEmail, string firstName,
            string lastName, string homeAddress, string homeCity, string homeState, DateTime dateOfBirth, 
            string country, string postalCode, string phoneNumber, string securityQuestion, 
            string securityAnswer)
        {
            bool result = false;
            const int DEFAULT_SETTINGS = 1;         // Using the default site settings
            const int INITIAL_GIFT_COUPON_BALANCE = 0; // Initial gift coupon balance

            // If the username is still free
            if (CheckUserNameAvailability(userName))
            {
                try
                {
                    // using transaction for consistancy
                    using (TransactionScope transaction = new TransactionScope())
                    {
                        // Filtering all input data for unwanted input
                        userName = Security.BindStringInput(userName, SecurityLevel.Two);
                        userPassword = Security.BindStringInput(userPassword, SecurityLevel.Two);
                        userEmail = Security.BindStringInput(userEmail, SecurityLevel.Two);
                        firstName = Security.BindStringInput(firstName, SecurityLevel.Two);
                        lastName = Security.BindStringInput(lastName, SecurityLevel.Two);
                        homeAddress = Security.BindStringInput(homeAddress, SecurityLevel.Two);
                        homeCity = Security.BindStringInput(homeCity, SecurityLevel.Two);
                        homeState = Security.BindStringInput(homeState, SecurityLevel.Two);
                        country = Security.BindStringInput(country, SecurityLevel.Two);
                        postalCode = Security.BindStringInput(postalCode, SecurityLevel.Two);
                        phoneNumber = Security.BindStringInput(phoneNumber, SecurityLevel.Two);
                        securityQuestion = Security.BindStringInput(securityQuestion, SecurityLevel.Two);
                        securityAnswer = Security.BindStringInput(securityAnswer, SecurityLevel.Two);

                        // Loadind the site settings
                        Settings siteSettings = Settings.Load(DEFAULT_SETTINGS);

                        // Creating a new confirmation code
                        string confirmationCode = Utility.CreateConfirmationCode();
                        // Encrypting the password for storing in database
                        string encryptedPassword = Security.Encrypt(userPassword, siteSettings.EncryptionKey, true);

                        // Creating the user account
                        SiteUser siteUser = SiteUser.Create(userName,encryptedPassword, userEmail,
                            false,false,false,confirmationCode,DateTime.Now,SqlDateTime.MinValue.Value,
                            securityQuestion,securityAnswer,0,HttpContext.Current.Request.UserHostAddress,
                            0,siteSettings.DefaultUserRoleId);
                           
                        // Creating the addresses for creating profile.
                        Address userAdderss = Address.Create("Profile Address", homeAddress, homeCity, 
                            homeState, country, phoneNumber, postalCode, firstName, lastName);
                        Address billingAddress = Address.Create("Billing Address", homeAddress, homeCity,
                            homeState, country, phoneNumber, postalCode, firstName, lastName);
                        Address shippingAddress = Address.Create("Shipping Address", homeAddress, homeCity,
                            homeState, country, phoneNumber, postalCode, firstName, lastName);

                        // Also creating the default profile for the user
                        UserProfile userProfile = UserProfile.Create(firstName, lastName, userAdderss.Id,
                            billingAddress.Id, shippingAddress.Id, siteSettings.DefaultTheme, siteSettings.DefaultTimeZone,
                            INITIAL_GIFT_COUPON_BALANCE, siteUser.Id, dateOfBirth);

                        // Sending the registration confirmation mail
                        if (MailManager.SendRegistrationMail(siteSettings, siteUser, userProfile))
                        {
                            // Complete the transaction, everything is perfectly done
                            transaction.Complete();
                            result = true;
                        }
                    }
                    return result;
                }
                catch (Exception ex)
                {
                    // Setting log information about the exception
                    StringBuilder logText = new StringBuilder("Registration caused server error: Parameters( ");
                    logText = logText.Append("userName -> ").Append(userName);
                    logText = logText.Append(",userPassword -> ").Append(userPassword);
                    logText = logText.Append(",userEmail -> ").Append(userEmail);
                    logText = logText.Append(",firstName -> ").Append(firstName);
                    logText = logText.Append(",lastName -> ").Append(lastName);
                    logText = logText.Append(",homeAddress -> ").Append(homeAddress);
                    logText = logText.Append(",homeCity -> ").Append(homeCity);
                    logText = logText.Append(",homeState -> ").Append(homeState);
                    logText = logText.Append(",dateOfBirth -> ").Append(dateOfBirth.ToString());
                    logText = logText.Append(",country -> ").Append(country);
                    logText = logText.Append(",postalCode -> ").Append(postalCode);
                    logText = logText.Append(",phoneNumber -> ").Append(phoneNumber);
                    logText = logText.Append(",securityQuestion -> ").Append(securityQuestion);
                    logText = logText.Append(",securityAnswer -> ").Append(securityAnswer);
                    logText = logText.Append(" )");

                    // Write the log into handled exception list
                    ILog log = LogHelper.GetHandledFileLogger("UserManager");
                    if (log.IsErrorEnabled)
                        log.Error(logText.ToString(), ex);

                    // return failure
                    return false;
                }
            }
            else
                return false;
        }

        /// <summary>
        /// Perform login operation for the users.
        /// </summary>
        /// <param name="userName">username of the user</param>
        /// <param name="userPassword">password of the user</param>
        /// <returns>True if login is successful, otherwise false</returns>
        public static bool LoginUser(string userName, string userPassword)
        {
            const int DEFAULT_USER = 0;         // Using the first user if multiple found
            const int DEFAULT_SETTINGS = 1;     // Loading the default site settings
            try
            {
                // Loading the site settings
                Settings siteSettings = Settings.Load(DEFAULT_SETTINGS);

                // Loading the user with the specified username and password
                SiteUser SiteUser = new SiteUser();
                List<Condition> conditions = new List<Condition>();

                userName = Security.BindStringInput(userName, SecurityLevel.Two);
                userPassword = Security.Encrypt(Security.BindStringInput(userPassword, SecurityLevel.Two)
                    , siteSettings.EncryptionKey, true); 

                conditions.Add(new Condition("user_name", ConditionOperator.Equal, userName, JoinOperator.FirstValue));
                conditions.Add(new Condition("user_password", ConditionOperator.Equal, userPassword, JoinOperator.And));

                List<SiteUser> SiteUsers = SiteUser.Load(conditions);

                // If at least one user is found
                if (SiteUsers.Count > DEFAULT_USER)
                {
                    // Update the last login time of the user
                    SiteUsers[DEFAULT_USER].LastLogin = DateTime.Now;

                    // If the user account is successfuly updated
                    if (SiteUsers[DEFAULT_USER].Save())
                    {
                        // Load the current user's basic information in the session
                        CurrentUser currentUser = CopyUserToCurrentUser(SiteUsers[DEFAULT_USER]);
                        SessionManager.LoggedUser = currentUser;
                        return true;
                    }
                }
                return false;
            }
            catch (Exception ex)
            {
                // Setting log information about the exception
                StringBuilder logText = new StringBuilder("Login caused server error: Parameters( ");
                logText = logText.Append("userName -> ").Append(userName);
                logText = logText.Append(",userPassword -> ").Append(userPassword);
                logText = logText.Append(" )");

                // Write the log into handled exception list
                ILog log = LogHelper.GetHandledFileLogger("UserManager");
                if (log.IsErrorEnabled)
                    log.Error(logText.ToString(), ex);

                // return failure
                return false;
            }
        }

        /// <summary>
        /// A helper method used by other methods in this class. 
        /// It basically loads the Current user object from 
        /// the site user object. The current user object holds
        /// imporatant data that needs to be frequenty used and
        /// kept in session to save database round trip.
        /// </summary>
        /// <param name="SiteUser">SiteUser object that needs to be loaded</param>
        /// <returns>CurrentUser object with loaded data</returns>
        private static CurrentUser CopyUserToCurrentUser(SiteUser SiteUser)
        {
            const int DEFAULT_PROFILE = 0;      // Using default profile, in case more is found

            string fullName = "New user";       // Setting the full name as New user in case the profile 
                                                // is not already created

            // If at least one profile is found
            if (SiteUser.ProfileUserProfile.Count > DEFAULT_PROFILE)
            {
                // Update the full name according to the profile information
                fullName = SiteUser.ProfileUserProfile[DEFAULT_PROFILE].FirstName + " " + 
                    SiteUser.ProfileUserProfile[DEFAULT_PROFILE].LastName;
            }

            // Create the CurrentUser object with the data
            CurrentUser currentUser = new CurrentUser(
                SiteUser.Id,
                SiteUser.UserName,
                SiteUser.EmailAddress,
                fullName,
                SiteUser.LastLogin,
                SiteUser.UserRoleId);

            return currentUser;
        }

        /// <summary>
        /// Finds a username and checks sends a confirmation link 
        /// for password change from which user can change their
        /// password.
        /// </summary>
        /// <param name="userName">username for whom to recover password</param>
        /// <returns>True if password change confirmation mail is sent, false otherwise</returns>
        public static bool RecoverPassword(string userName)
        {
            const int DEFAULT_USER = 0;         // Using the first user as default
            const int DEFAULT_PROFILE = 0;      // Using the first profile as default
            const int DEFAULT_SETTINGS = 1;     // Using the first site settings as default

            bool result = false;
            try
            {
                // Checking whether the username is in valid format
                if (string.IsNullOrEmpty(userName) || !RegExUtil.IsCustomMatch(userName, RegExUtil.UserNameFormat))
                    return false;

                // Using transaction to maintain consistancy
                using (TransactionScope transaction = new TransactionScope())
                {
                    // Loading the site user that match the username
                    SiteUser SiteUser = new SiteUser();
                    List<Condition> conditions = new List<Condition>();
                    userName = Security.BindStringInput(userName, SecurityLevel.Two);
                    conditions.Add(new Condition("user_name", ConditionOperator.Equal, userName, JoinOperator.FirstValue));
                    List<SiteUser> SiteUsers = SiteUser.Load(conditions);

                    // If at least one user is found that match the id
                    if (SiteUsers.Count > DEFAULT_USER)
                    {
                        // If the account has been verified
                        if (SiteUsers[DEFAULT_USER].IsConfirmed)
                        {
                            // Create a new confirmation code for recover password verification mail
                            string confirmationCode = Utility.CreateConfirmationCode();
                            SiteUsers[DEFAULT_USER].ConfirmationCode = confirmationCode;

                            // If the user is updated successfuly
                            if (SiteUsers[DEFAULT_USER].Save())
                            {
                                // Loading the site settings
                                Settings siteSettings = Settings.Load(DEFAULT_SETTINGS);
                                UserProfile userProfile = null;

                                // If the user account has a profile, load it
                                if (SiteUsers[DEFAULT_USER].ProfileUserProfile.Count > DEFAULT_PROFILE)
                                    userProfile = SiteUsers[DEFAULT_USER].ProfileUserProfile[DEFAULT_PROFILE];

                                // send the password recover verification mail
                                if (MailManager.SendPasswordChangeMail(siteSettings, SiteUsers[DEFAULT_USER],
                                    userProfile, true))
                                {
                                    // complete the transaction, every thing is successful
                                    transaction.Complete();
                                    result = true;
                                }
                            }
                        }
                    }
                }
                return result;
            }
            catch (Exception ex)
            {
                // Setting log information about the exception
                StringBuilder logText = new StringBuilder("Recovery password caused server error: Parameters( ");
                logText = logText.Append("userName -> ").Append(userName);
                logText = logText.Append(" )");

                // Write the log into handled exception list
                ILog log = LogHelper.GetHandledFileLogger("UserManager");
                if (log.IsErrorEnabled)
                    log.Error(logText.ToString(), ex);

                // return failure
                return false;
            }
        }

        /// <summary>
        /// Changes password from password recovery page. Verifies the user confirmation
        /// mail and then sets the new password
        /// </summary>
        /// <param name="userName">username of the user for whom to change the password</param>
        /// <param name="confirmationCode">confirmation code of the verification mail</param>
        /// <param name="newPassword">New password for the user account</param>
        /// <returns>True if password change succeed, otherwise false</returns>
        public static bool ChangePasswordForPasswordRecovery(string userName, string securityAnswer,
            string confirmationCode, string newPassword)
        {
            const int DEFAULT_PROFILE = 0;          // Using the first profile, if multiple found
            const int DEFAULT_SETTINGS = 1;         // Using the site settings that has Id 1
            bool result = false;
            UserProfile userProfile = null;

            try
            {
                // Using transaction to maintain consistancy
                using (TransactionScope transaction = new TransactionScope())
                {
                    // Testing whether the username and confirmation code combination is valid
                    SiteUser SiteUser = VerifyConfirmationCode(userName, confirmationCode);
                    // Loading the settings for the site.
                    Settings siteSettings = Settings.Load(DEFAULT_SETTINGS);

                    // If the site user is verified and identified
                    if (SiteUser != null && SiteUser.SecurityAnswer.ToLower() == securityAnswer.ToLower())
                    {
                        // Set a new password for the user
                        SiteUser.UserPassword = Security.Encrypt(Security.BindStringInput(newPassword, 
                            SecurityLevel.Two),siteSettings.EncryptionKey,true);

                        // Clear the confirmation code to invalidate repeatative request
                        SiteUser.ConfirmationCode = string.Empty;

                        // If the user is successfuly saved
                        if (SiteUser.Save())
                        {
                            // Collect the profile of the user
                            if (SiteUser.ProfileUserProfile.Count > DEFAULT_PROFILE)
                                userProfile = SiteUser.ProfileUserProfile[DEFAULT_PROFILE];

                            // Send password change successful message and with new password 
                            if (MailManager.SendPasswordChangeInformationMail(siteSettings, SiteUser,
                                userProfile))
                            {
                                // Complete the transaction, every thing is properly done
                                transaction.Complete();
                                result = true;
                            }
                        }
                    }
                }
                return result;
            }
            catch (Exception ex)
            {
                // Setting log information about the exception
                StringBuilder logText = new StringBuilder("Registration verification caused server error: Parameters( ");
                logText = logText.Append("userName -> ").Append(userName);
                logText = logText.Append(",confirmationCode -> ").Append(confirmationCode);
                logText = logText.Append(",newPassword -> ").Append(newPassword);
                logText = logText.Append(" )");

                // Write the log into handled exception list
                ILog log = LogHelper.GetHandledFileLogger("UserManager");
                if (log.IsErrorEnabled)
                    log.Error(logText.ToString(), ex);

                // return failure
                return false;
            }
        }

        public static string GetSecurityQuestion(string userName)
        {
            const int DEFAULT_USER = 0;         // Using the first user as default

            try
            {
                // Loading the site user that match the username
                SiteUser SiteUser = new SiteUser();
                List<Condition> conditions = new List<Condition>();
                userName = Security.BindStringInput(userName, SecurityLevel.Two);
                conditions.Add(new Condition("user_name", ConditionOperator.Equal, userName, JoinOperator.FirstValue));
                List<SiteUser> SiteUsers = SiteUser.Load(conditions);

                // If at least one user is found
                if (SiteUsers.Count > DEFAULT_USER)
                    return SiteUsers[DEFAULT_USER].SecurityQuestion;
                else
                    return null;
            }
            catch(Exception ex)
            {
                // Setting log information about the exception
                StringBuilder logText = new StringBuilder("Security question retrive server error: Parameters( ");
                logText = logText.Append("userName -> ").Append(userName);
                logText = logText.Append(" )");

                // Write the log into handled exception list
                ILog log = LogHelper.GetHandledFileLogger("UserManager");
                if (log.IsErrorEnabled)
                    log.Error(logText.ToString(), ex);

                // return failure
                return null;
            }
        }
    }
}